﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SymWorld.Model
{
    public static class ParticleStateRelativeCalculator
    {
        public static ParticleState CreateNextParticleStateRelative(this ParticleState prevParticleState, WorldState nextWorldState, double delay)
        {
            // count force
            Vector force = CalcNextForce(prevParticleState, delay);

            // count acceleration
            Vector acceleration = VectorExtension.Multiply(force, 1 / prevParticleState.ParticleKind.Mass);

            // count velocity
            //Vector velocity = VectorExtension.Sum(this.Velocity, VectorExtension.Multiply(acceleration, delay)); // simple speed
            Vector velocity = VectorExtension.Sum(prevParticleState.Velocity, VectorExtension.Multiply(acceleration, delay)); // relative speed

            // count position
            Vector position = VectorExtension.Sum(prevParticleState.Position, VectorExtension.Multiply(velocity, delay));

            // construct new ParticleState object with results
            ParticleState retNextParticleState;
            retNextParticleState = new ParticleState(nextWorldState, prevParticleState.ParticleKind, position, velocity);

            return retNextParticleState;
        }

        private static Vector CalcNextForce(ParticleState prevParticleState, double delay)
        {
            // count force
            Vector force = new Vector();
            // net force is sum of all forces from all other particles
            foreach (ParticleState particleState in prevParticleState.WorldState.ParticleStateList)
                if (particleState != prevParticleState)
                    force = VectorExtension.Sum(force, CalcNextForce(prevParticleState, particleState, delay)); // calc force for other particle

            return force;
        }

        private static Vector CalcNextForce(ParticleState prevParticleState, ParticleState otherParticleState, double delay)
        {
            // count force for other particle
            ParticleState forcingOtherParticleState = otherParticleState;
            double timeDifference = 0d;
            double forcingDistance;

            // count time-space distance between particles in light-time units
            WorldState forcingWorldState = prevParticleState.WorldState;
            while (timeDifference < (forcingDistance = VectorExtension.Distance(forcingOtherParticleState.Position, prevParticleState.Position)))
            {
                timeDifference += forcingWorldState.Delay;

                ParticleState otherParticleHistoricState = forcingOtherParticleState.GetPrevParticleState();
                WorldState historicWorldState = forcingWorldState.PrevWorldState;

                if (historicWorldState == null || otherParticleHistoricState == null)
                    break;

                forcingOtherParticleState = otherParticleHistoricState;
                forcingWorldState = historicWorldState;
            }

            // check if age of the universe and distance allows force to appear
            if (forcingOtherParticleState != null && timeDifference > forcingDistance)
            {
                // create interpolated other particle state for better accuracy
                ParticleState interpolatedOtherParticleState = new ParticleState(null, forcingOtherParticleState.ParticleKind, null, null);

                // count force direction
                Vector forceDirection = VectorExtension.Normalized(VectorExtension.Difference(prevParticleState.Position, forcingOtherParticleState.Position));

                // count force value for this other particle
                double forceValue =
                    prevParticleState.ParticleKind.ElectricCharge * forcingOtherParticleState.ParticleKind.ElectricCharge
                    / timeDifference / timeDifference;

                return VectorExtension.Multiply(forceDirection, forceValue);
            }
            return new Vector();
        }

        private static double CalcRelativeDistance(ParticleState prevParticleState, ParticleState otherParticleState)
        {
            Vector prevParticlePosition = prevParticleState.Position;
            // count force for other particle
            
            double timeDifference = 0d;
            double prevTimeDifference = 0d;

            double spaceDifference = 0d;
            double prevSpaceDifference = 0d;

            // count time-space distance between particles in light-time units
            ParticleState forcingOtherParticleState = otherParticleState;
            ParticleState prevForcingOtherParticleState = null;
            WorldState forcingWorldState = prevParticleState.WorldState;
            WorldState prevForcingWorldState = null;

            while (timeDifference < (spaceDifference = VectorExtension.Distance(forcingOtherParticleState.Position, prevParticlePosition)))
            {
                prevTimeDifference = timeDifference;
                timeDifference += forcingWorldState.Delay;

                ParticleState otherParticleHistoricState = forcingOtherParticleState.GetPrevParticleState();
                WorldState historicWorldState = forcingWorldState.PrevWorldState;

                if (historicWorldState == null || otherParticleHistoricState == null)
                    return double.NaN; // dont have data 

                prevForcingOtherParticleState = forcingOtherParticleState;
                forcingOtherParticleState = otherParticleHistoricState;
                prevForcingWorldState = forcingWorldState;
                forcingWorldState = historicWorldState;
                prevSpaceDifference = spaceDifference;
            }

            // interpolate
            double t1 = timeDifference;
            double t2 = prevTimeDifference;
            double d1 = spaceDifference;
            double d2 = prevSpaceDifference;
            double interpolatedTimeSpaceDistance = (d2 - d1) * ((d1 - t1) / (t2 - d2 + d1 - t1)) + d1;

            return interpolatedTimeSpaceDistance;
        }
    }
}
